/* shttpd - install and delete edge triggers (AKA epoll on Linux)
   Copyright (C) 2018 Ariadne Devos

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <sHT/bugs.h>
#include <sHT/compiler.h>
#include <sHT/scheduling.h>
#include <sHT/test.h>
#include "worker.h"
#include "fd.h"

#include <errno.h>
#include <stddef.h>
#include <sys/epoll.h>

int
sHT_alloc_watch_set(void)
{
	int fd = epoll_create1(EPOLL_CLOEXEC);
	if (!sHT_lt0(fd))
		/* Invalid values may speculatively be returned. */
		return fd;
	/* Invalid values may speculatively be returned. */
	int err = errno;
	switch (err) {
	case EMFILE:
		sHT_hide_var(err);
		return sHT_WATCH_SET_ALLOC_EMFILE;
	case ENFILE:
		sHT_hide_var(err);
		return sHT_WATCH_SET_ALLOC_ENFILE;
	case ENOMEM:
		sHT_hide_var(err);
		return sHT_WATCH_SET_ALLOC_ENOMEM;
	default:
		sHT_hide_var(err);
		/* If the kernel is acting strangely, report this.
		   If we are on a speculative execution -- there is a bug
		   in the kernel, worry about *that* instead. */
		sHT_halt("alloc_event_polling: errno valid?");
		/* Everything may be a speculative placeholder! */
		return 0;
	}
}

void
sHT_free_watch_set(int watches)
{
	sHT_close(watches);
}

/* When using /dev/kqueue is supported, rename this file to worker/edge-epoll.c
   Looking at FreeBSD's man page on that
 */

/* ... except for EPOLLONESHOT, EPOLLWAKEUP and EPOLLEXCLUSIVE.
   Note EPOLLET, which lets polling be edge-triggered instead of
   level-triggered.
 */
#define sHT_EPOLL_ALL (EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLPRI | EPOLLERR | \
  EPOLLHUP | EPOLLET)

/* TODO: perhaps a string table (see Ulrich Drepper's optimisation tutorial) */
static const struct sHT_bug_option sHT_epoll_ctl_bugs[] =
  { { .code = EBADFD, .msg = "not a fd" },
    { .code = EEXIST, .msg = "only one trigger at the time" },
    { .code = EINVAL, .msg = "bad fd type for trigger" },
    { .code = EPERM, .msg = "bad fd type for trigger" },
    { .code = ENOENT, .msg = "fd has no trigger" },
    { .code = ELOOP, .msg = "bad fd type for trigger" },
    { .code = 0, .msg = "worker/egde-epoll.c" } };

enum sHT_watch_set_install
sHT_install_edge_trigger(sHT_watch_set watches, int fd, struct sHT_task *task)
{
	struct epoll_event e = {
		.events = sHT_EPOLL_ALL,
		.data = { .ptr = task },
	};
	int ret = epoll_ctl(watches, EPOLL_CTL_ADD, fd, &e);
	if (!sHT_nonzero_p(ret))
		return 0;
	int err = errno;
	switch (err) {
	case ENOSPC:
		sHT_hide_var(err);
		return sHT_WATCH_SET_INSTALL_LIMIT;
	case ENOMEM:
		sHT_hide_var(err);
		return sHT_WATCH_SET_INSTALL_ENOMEM;
	default:
		sHT_hide_var(err);
		sHT_bug(sHT_epoll_ctl_bugs, err);
		/* Everything may be a speculative placeholder! */
		return 0;
	}
}

void
sHT_delete_edge_trigger(sHT_watch_set watches, int fd)
{
	/* TODO: I remember reading the kernel source, looking for memory
	   allocations. I don't think there were any in case of
	   EPOLL_CTL_DELETE, do the analysis again and refine the man page.
	   I assume this cannot happen. */
	/* TODO: NULL is not portable to Linux < 2.6.9. */
	int ret = epoll_ctl(watches, EPOLL_CTL_DEL, fd, NULL);
	if (!sHT_nonzero_p(ret))
		return;
	sHT_bug(sHT_epoll_ctl_bugs, errno);
}
